/*
    File:       UDPSocket.cpp

    Contains:   Implementation of object defined in UDPSocket.h.

    
    
*/

#ifndef __Win32__
#include <sys/types.h>
#include <sys/socket.h>
#endif

#include <errno.h>
#include "OS.h"
#include "UDPSocket.h"
#include "OSThread.h"


#define INVALID_FD	(-1)

UDPSocket::UDPSocket()
{       
    //setup msghdr
    ::memset(&fMsgAddr, 0, sizeof(fMsgAddr));
}

OS_Error UDPSocket::SetRemoteAddr(UInt32 inRemoteAddr, UInt16 inRemotePort)
{
    m_theRemoteAddr.sin_family = AF_INET;
    m_theRemoteAddr.sin_port = htons(inRemotePort);
    m_theRemoteAddr.sin_addr.s_addr = htonl(inRemoteAddr);
    
    //connect to remote so that use writeV
    if(fFileDesc != INVALID_FD)
    {
	int err = ::connect(fFileDesc, (sockaddr *)&m_theRemoteAddr, sizeof(m_theRemoteAddr));
	
	if (err == -1)
	{
		return (OS_Error)OS::GetErrno();
	}

	fState |= kConnected;
    }
    return OS_NoErr;
}

OS_Error UDPSocket::SendTo(const char* inBuffer, UInt32 inLength)
{
    Assert(inBuffer != NULL);
    
    // Win32 says that inBuffer is a char*
	int theErr = ::sendto(fFileDesc, inBuffer, inLength, 0, (sockaddr*)&m_theRemoteAddr, sizeof(m_theRemoteAddr));
	
    if (theErr == -1)
        return (OS_Error)OS::GetErrno();
    return OS_NoErr;
}

OS_Error UDPSocket::SendTo(UInt32 inRemoteAddr, UInt16 inRemotePort, void* inBuffer, UInt32 inLength)
{
    Assert(inBuffer != NULL);
    
    struct sockaddr_in  theTmpRemoteAddr;
    theTmpRemoteAddr.sin_family = AF_INET;
    theTmpRemoteAddr.sin_port = htons(inRemotePort);
    theTmpRemoteAddr.sin_addr.s_addr = htonl(inRemoteAddr);


    // Win32 says that inBuffer is a char*
	int theErr = ::sendto(fFileDesc, (char*)inBuffer, inLength, 0, (sockaddr*)&theTmpRemoteAddr, sizeof(theTmpRemoteAddr));


    if (theErr == -1)
        return (OS_Error)OS::GetErrno();
    return OS_NoErr;
}

OS_Error UDPSocket::RecvFrom(void* ioBuffer, UInt32 inBufLen, UInt32* outRecvLen)
{
    Assert(outRecvLen != NULL);
    
    // Win32 says that ioBuffer is a char*
    Int32 theRecvLen = ::recvfrom(fFileDesc, (char*)ioBuffer, inBufLen, 0, NULL, NULL);
	
    if (theRecvLen == -1)
        return (OS_Error)OS::GetErrno();
    
    Assert(theRecvLen >= 0);

    *outRecvLen = (UInt32)theRecvLen;
    return OS_NoErr; 
}

OS_Error UDPSocket::RecvFrom(UInt32* outRemoteAddr, UInt16* outRemotePort,
                            void* ioBuffer, UInt32 inBufLen, UInt32* outRecvLen)
{
    Assert(outRecvLen != NULL);
    Assert(outRemoteAddr != NULL);
    Assert(outRemotePort != NULL);
    
#if __Win32__ 
    int addrLen = sizeof(fMsgAddr);
#else
    socklen_t addrLen = sizeof(fMsgAddr);
#endif


    // Win32 says that ioBuffer is a char*
    Int32 theRecvLen = ::recvfrom(fFileDesc, (char*)ioBuffer, inBufLen, 0, (sockaddr*)&fMsgAddr, &addrLen);


    if (theRecvLen == -1)
        return (OS_Error)OS::GetErrno();
    
    *outRemoteAddr = ntohl(fMsgAddr.sin_addr.s_addr);
    *outRemotePort = ntohs(fMsgAddr.sin_port);
    Assert(theRecvLen >= 0);
    *outRecvLen = (UInt32)theRecvLen;
    return OS_NoErr;        
}

OS_Error UDPSocket::JoinMulticast(UInt32 inRemoteAddr)
{
    struct ip_mreq  theMulti;
	UInt32 localAddr = fLocalAddr.sin_addr.s_addr; // Already in network byte order

    theMulti.imr_multiaddr.s_addr = htonl(inRemoteAddr);
    theMulti.imr_interface.s_addr = localAddr;
    int err = setsockopt(fFileDesc, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&theMulti, sizeof(theMulti));
    //AssertV(err == 0, OS::GetErrno());
    if (err == -1)
         return (OS_Error)OS::GetErrno();
    else
         return OS_NoErr;
}

OS_Error UDPSocket::SetTtl(UInt16 timeToLive)
{
    // set the ttl
    u_char  nOptVal = (u_char)timeToLive;//dms - stevens pp. 496. bsd implementations barf
                                            //unless this is a u_char
    int err = setsockopt(fFileDesc, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&nOptVal, sizeof(nOptVal));
    if (err == -1)
        return (OS_Error)OS::GetErrno();
    else
        return OS_NoErr;    
}

OS_Error UDPSocket::SetMulticastInterface(UInt32 inLocalAddr)
{
    // set the outgoing interface for multicast datagrams on this socket
    in_addr theLocalAddr;
    theLocalAddr.s_addr = inLocalAddr;
    int err = setsockopt(fFileDesc, IPPROTO_IP, IP_MULTICAST_IF, (char*)&theLocalAddr, sizeof(theLocalAddr));
   // Assert(err == 0);
    if (err == -1)
        return (OS_Error)OS::GetErrno();
    else
        return OS_NoErr;    
}

OS_Error UDPSocket::LeaveMulticast(UInt32 inRemoteAddr)
{
    struct ip_mreq  theMulti;
    theMulti.imr_multiaddr.s_addr = htonl(inRemoteAddr);
    theMulti.imr_interface.s_addr = htonl(fLocalAddr.sin_addr.s_addr);
    int err = setsockopt(fFileDesc, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&theMulti, sizeof(theMulti));
    if (err == -1)
        return (OS_Error)OS::GetErrno();
    else
        return OS_NoErr;    
}
